/*
 * Copyright (c) 2011 Auzingers
 * Created by Oui-IT
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.auzingers.docsorganizer.gui;

import com.auzingers.docsorganizer.DocsOrganizerApplication;
import com.auzingers.docsorganizer.R;
import com.auzingers.docsorganizer.cache.HistoryCache;
import com.auzingers.docsorganizer.entity.GoogleCollection;
import com.auzingers.docsorganizer.entity.GoogleDocument;
import com.auzingers.docsorganizer.entity.GoogleEntry;
import com.auzingers.docsorganizer.entity.GoogleResult;
import com.auzingers.docsorganizer.exception.UserCanceledOperation;
import com.auzingers.docsorganizer.googleapi.GoogleAPI;
import com.auzingers.docsorganizer.gui.DialogUtil.IInputReceived;
import com.auzingers.docsorganizer.gui.chooseaccount.GetAuthTokenByTypeAndFeaturesTask;
import com.auzingers.docsorganizer.gui.task.AbstractSimpleAsyncTask;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Main activity of the docsorganizer application.
 */
public final class GoogleDocMain extends AbstractGoogleListActivity {

    //static final String TAG = DocsOrganizerApplication.APP_TAG + ".GoogleDocMain";

    private static final int ANDROID_VERSION_ICS = 14;

    private static final String TITLE_MAIN = "Docs Organizer";

    private static final String TITLE_CHOOSE = "Please choose Google account";

    private static final int MENU_ACCOUNTS = 1;

    private static final int MENU_REFRESH = 2;

    private static final int CTX_MENU_DELETE = 101;

    private static final int CTX_MENU_MOVE = 102;

    private static final int CTX_MENU_COPY = 103;

    private static final int REQUEST_AUTHENTICATE = 0;

    private static final int REQUEST_MOVE_ITEM = 1;

    /** Logging level for HTTP requests/responses. */
    private static final Level LOGGING_LEVEL = Level.CONFIG;

    private static final String AUTH_TOKEN_TYPE = "writely";

    private volatile boolean firstAccountSelection = true;

    /** Title of the current collection. */
    private String currentCollectionTitle = "";

    /** Startkey for next paging. */
    private String nextStartKey = null;

    /** Textview to display if there are more elements to load. */
    private TextView txtLoadMore;

    /** Stores the selected item position between onCreateContextMenu and onContextItemSelected. */
    private int selectedContextPosition = -1;

    private Handler chooseAccountHandler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Logger.getLogger("com.google.api.client").setLevel(LOGGING_LEVEL);
        setTitle(TITLE_MAIN);
        setupGui();
    }

    @Override
    void dbServiceConnected() {
        super.dbServiceConnected();
        gotAccount();
    }

    @Override
    protected void getControls() {
        setSpinningWheel(findViewById(R.id.gd_ly_wheel));
        setRefreshImage((ImageView) findViewById(R.id.gd_wheel));
        setListView((ListView) findViewById(R.id.gd_listview));
        setTxtListEmpty((TextView) findViewById(R.id.gd_empty));
        txtLoadMore = (TextView) findViewById(R.id.gd_load_more);
    }

    @Override
    protected void addActionListeners() {
        super.addActionListeners();
        txtLoadMore.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                executeDownloadMoreDocuments();
            }
        });
    }

    @Override
    protected int getContentView() {
        return R.layout.googledocmain;
    }

    @Override
    protected void setupGui() {
        super.setupGui();
        registerForContextMenu(getListView());
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent pData) {
        super.onActivityResult(requestCode, resultCode, pData);
        switch (requestCode) {
            case REQUEST_AUTHENTICATE:
                if (resultCode == RESULT_OK) {
                    gotAccount();
                } else {
                    chooseAccount();
                }
                break;
            case REQUEST_MOVE_ITEM:
                if (resultCode == RESULT_OK) {
                    final String resourceId = pData.getStringExtra(CollectionView.RESULT_SELECTED_RESOURCE_ID);
                    moveResourceTo(resourceId);
                }
                break;
            default:
                //nothing to do
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (getGoogleUtil().getAccountManager().getAccounts().length >= 2) {
            menu.add(0, MENU_ACCOUNTS, 0, getString(R.string.switch_account));
        }
        menu.add(0, MENU_REFRESH, 1, "Refresh");
        return true;
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        selectedContextPosition = ((AdapterView.AdapterContextMenuInfo) menuInfo).position;
        super.onCreateContextMenu(menu, v, menuInfo);
        menu.add(0, CTX_MENU_DELETE, 0, "Delete");
        menu.add(0, CTX_MENU_MOVE, 1, "Move");
        if (getSelectedItemForContextMenu() instanceof GoogleDocument) {
            menu.add(0, CTX_MENU_COPY, 2, "Copy");
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ACCOUNTS:
                switchAccount();
                return true;
            case MENU_REFRESH:
                executeRefreshDocuments();
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case CTX_MENU_DELETE:
                deleteCurrentItem();
                return true;
            case CTX_MENU_COPY:
                copyCurrentDocument();
                return true;
            case CTX_MENU_MOVE:
                moveCurrentItem();
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    /**
     * Performs switch to another Google account.
     */
    private void switchAccount() {
        executeSafely(new DOProcess() {
            public void run() throws Exception {
                getHistoryCache().clear();
                getDb().clearCollections();
                chooseAccount();
            }
        });
    }

    /**
     * Tries to receive an account from the account manager, if successful, an authorization token is
     * requested, stored and the list is refreshed. If no account was returned, the user has to choose
     * an account and chooseAccount() is called.
     */
    private void gotAccount() {
        final Account account = getGoogleUtil().getCurrentAccount();
        if (account != null) {
            // handle invalid token
            if (getGoogleUtil().getInitializerAuthToken() == null) {
                getGoogleUtil().getAccountManager().manager.getAuthToken(account, AUTH_TOKEN_TYPE, true,
                        new AccountManagerCallback<Bundle>() {
                            public void run(AccountManagerFuture<Bundle> future) {
                                try {
                                    final Bundle bundle = future.getResult();
                                    if (bundle.containsKey(AccountManager.KEY_INTENT)) {
                                        final Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
                                        intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
                                        startActivityForResult(intent, REQUEST_AUTHENTICATE);
                                    } else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
                                        getGoogleUtil().setAuthToken(bundle.getString(AccountManager.KEY_AUTHTOKEN));
                                        executeRefreshDocuments();
                                    }
                                } catch (Exception e) {
                                    handleAccountException(e);
                                }
                            }
                        }, null);
            } else {
                executeRefreshDocuments();
            }
        } else {
            chooseAccount();
        }
    }

    private AccountManager getAccountManager() {
        return getGoogleUtil().getAccountManager().manager;
    }

    /**
     * Gets all system accounts and shows an activity to select an account if there is more than one.
     * @param accountType type of account
     * @param authTokenType token type
     * @param features requested features
     * @param activityForPrompting parent activity
     * @param addAccountOptions options for adding accounts
     * @param getAuthTokenOptions token options
     * @param callback callback that is called when a result has been retrieved
     * @param handler handler
     * @return Future to receiver results from
     */
    public AccountManagerFuture<Bundle> getAuthTokenByFeatures(final String accountType, final String authTokenType,
            final String[] features, final Activity activityForPrompting, final Bundle addAccountOptions,
            final Bundle getAuthTokenOptions, final AccountManagerCallback<Bundle> callback, final Handler handler) {

        if (accountType == null) {
            throw new IllegalArgumentException("account type is null");
        }
        if (authTokenType == null) {
            throw new IllegalArgumentException("authTokenType is null");
        }
        final GetAuthTokenByTypeAndFeaturesTask task = new GetAuthTokenByTypeAndFeaturesTask(accountType,
                authTokenType, features, activityForPrompting, addAccountOptions, getAuthTokenOptions, callback,
                handler, getAccountManager(), this);
        task.start();
        return task;
    }

    /**
     * Performs an update of data shown in the document list after an account change (both on success and error).
     */
    private void updateDataAfterAccountSelection() {
        stopAnimation();
        if (firstAccountSelection) {
            clearData();
        }
    }

    /**
     * Calls the account manager activity, so that the user can choose an account.
     */
    private void chooseAccount() {
        getDb().clearCollections();
        setTitle(TITLE_CHOOSE);

        if (Build.VERSION.SDK_INT >= ANDROID_VERSION_ICS) {
            //not using internal apis for version 4.0 and higher (because then the title is ok by default)
            getGoogleUtil().getAccountManager().manager.getAuthTokenByFeatures(GoogleAccountManager.ACCOUNT_TYPE,
                    AUTH_TOKEN_TYPE, null, GoogleDocMain.this, null, null, new DocsOrganizerAccountManagerCallback(),
                    null);
        } else {
            //using interal apis for version <4.0, because then the title shows "Android system"
            getAuthTokenByFeatures(GoogleAccountManager.ACCOUNT_TYPE, AUTH_TOKEN_TYPE, null, GoogleDocMain.this, null,
                    null, new DocsOrganizerAccountManagerCallback(), chooseAccountHandler);
        }
    }

    /**
     * Requeries documents from google and refreshes the list.
     */
    void executeRefreshDocuments() {
        nextStartKey = null;
        txtLoadMore.setVisibility(View.GONE);
        setListAdapter(new GoogleEntryListAdapter(GoogleDocMain.this, Collections.<GoogleEntry> emptyList()));
        startAnimation();
        clearData();
        new Thread(new RefreshDocumentsRunnable()).start();
    }

    /**
     * Downloads the next bunch of documents.
     */
    void executeDownloadMoreDocuments() {
        txtLoadMore.setVisibility(View.GONE);
        startAnimation();
        new Thread(new RefreshDocumentsRunnable()).start();
    }

    /**
     * Handles exception during login to Google.
     *
     * @param e Exception
     */
    void handleAccountException(Exception e) {
        e.printStackTrace();
        if (e instanceof HttpResponseException) {
            final HttpResponse response = ((HttpResponseException) e).getResponse();
            final int statusCode = response.getStatusCode();
            try {
                response.ignore();
            } catch (IOException e1) {
                Log.w(TAG, "Exception during response.ignore", e1);
            }
            if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
                gotAccount();
                return;
            }
            try {
                Log.e(TAG, response.parseAsString());
            } catch (IOException ex) {
                Log.w(TAG, "Error parsing result", ex);
            }
        }
        Log.e(TAG, e.getMessage(), e);
    }

    /**
     * Deletes the currently selected document.
     */
    private void deleteCurrentItem() {
        final GoogleEntry selected = getSelectedItemForContextMenu();
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        final String message;
        if (selected.isSharedEntry()) {
            message = "You are not the owner of '" + selected.getTitle() + "'. Do you want to remove it from the list?";
        } else {
            message = "Do you really want to delete '" + selected.getTitle() + "'?";
        }
        builder.setMessage(message);
        builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                new DeleteEntryTask().execute(selected);
            }
        });
        builder.setNegativeButton("No", null);
        builder.create().show();
    }

    /**
     * Moves the currently selected resource to the given target folder.
     * @param targetResourceId simple resource id of the target folder
     */
    private void moveResourceTo(final String targetResourceId) {
        new MoveDocumentTask().execute(targetResourceId);
    }

    /** Copies a document. */
    private void copyCurrentDocument() {
        final GoogleEntry selected = getSelectedItemForContextMenu();
        DialogUtil.showInputDialog(this, "Copy document", "Please enter new document name", new IInputReceived() {
            public void processInput(final String input) {
                if (input == null || input.trim().length() == 0) {
                    Toast.makeText(GoogleDocMain.this, "No target document name", Toast.LENGTH_LONG).show();
                } else {
                    new CopyDocumentTask(input).execute(selected);
                }
            }
        });
    }

    /**
     * Returns the currently item that was selected the last time the user selected an option of the context menu.
     * @return the item or null
     */
    private GoogleEntry getSelectedItemForContextMenu() {
        final GoogleEntry result;
        if (selectedContextPosition >= 0) {
            result = (GoogleEntry) getListAdapter().getItem(selectedContextPosition);
        } else {
            result = null;
        }
        return result;
    }

    /**
     * Moves the currently selected document to another collection.
     */
    private void moveCurrentItem() {
        final GoogleEntry selected = getSelectedItemForContextMenu();
        final Intent intent = new Intent(this, CollectionView.class);
        intent.putExtra(CollectionView.PARAM_RESOURCE_TITLE, selected.getTitle());
        startActivityForResult(intent, REQUEST_MOVE_ITEM);
    }

    private HistoryCache getHistoryCache() {
        return ((DocsOrganizerApplication) getApplicationContext()).getHistoryCache();
    }

    @Override
    protected void itemSelected(final int position) {
        final GoogleEntry entry = getData().get(position);
        final HistoryCache historyCache = getHistoryCache();
        if (entry instanceof GoogleCollection) {
            getHistoryCache().push(getCurrentCollectionId());
            historyCache.setCurrentCollectionId(entry.getSimpleResourceId());
            currentCollectionTitle = entry.getTitle();
            showTitle();
            getDb().saveCollection((GoogleCollection) entry);
            executeRefreshDocuments();
        }
    }

    @Override
    public void onBackPressed() {
        if (!isProcessing()) {
            final HistoryCache historyCache = getHistoryCache();
            if (historyCache.isEmpty()) {
                DialogUtil.showQuestionDialog(this, "Quit?", "Do you really want to exit the application?",
                        new Runnable() {
                            public void run() {
                                finish();
                                System.exit(0);
                            }
                        }, null);
            } else {
                hideEmptyText();
                final String currentCollectionId = historyCache.pop();
                historyCache.setCurrentCollectionId(currentCollectionId);
                currentCollectionTitle = getDb().getCollectionName(currentCollectionId);
                showTitle();
                nextStartKey = null;
                executeRefreshDocuments();
            }
        }
    }

    @Override
    protected void downloadComplete() {
        super.downloadComplete();
        showTitle();
        showLoadMore();
    }

    /**
     * Shows the text "load more" if there are more elements.
     */
    private void showLoadMore() {
        if (nextStartKey == null) {
            txtLoadMore.setVisibility(View.GONE);
        } else {
            txtLoadMore.setVisibility(View.VISIBLE);
        }
    }

    /**
     * Reads the title of the current collection from the database.
     */
    protected void readTitleOfCurrentCollection() {
        final String currentCollectionId = getCurrentCollectionId();
        if (currentCollectionId == "root") {
            currentCollectionTitle = "Root";
        } else {
            currentCollectionTitle = getDb().getCollectionName(currentCollectionId);
        }
    }

    /**
     * Displays the name of the current collection in the title.
     */
    protected void showTitle() {
        if (currentCollectionTitle == null) {
            setTitle(TITLE_MAIN + " - Collection 'Root'");
        } else {
            setTitle(TITLE_MAIN + " - Collection '" + currentCollectionTitle + "'");
        }
    }

    protected String getCurrentCollectionId() {
        return getHistoryCache().getCurrentCollectionId();
    }

    // -------------------------------- INNER CLASSES ---------------------------------

    /**
     * Background thread for retrieving document and collection data from Google.
     */
    private class RefreshDocumentsRunnable implements Runnable {
        /** {@inheritDoc} */
        public void run() {
            try {
                final GoogleAPI api = getGoogleUtil().getAPI();
                final GoogleResult result = api.getElementsOfCollection(getCurrentCollectionId(), nextStartKey);
                final Set<GoogleEntry> newData = new HashSet<GoogleEntry>();
                newData.addAll(result.getEntries());
                if ("root".equals(getCurrentCollectionId())) {
                    final String mail = getGoogleUtil().getCurrentAccount().name;
                    newData.addAll(api.getSharedDocumentsWithoutParent(getDb(), mail));
                }
                setData(new ArrayList<GoogleEntry>(newData));
                Collections.sort(getData());
                nextStartKey = result.getNextStartKey();
                readTitleOfCurrentCollection();
                final Message msg = new Message();
                msg.what = MSG_DOWNLOAD_COMPLETE;
                getDownloadHandler().sendMessage(msg);
            } catch (Throwable ie) {
                final Message msg = new Message();
                msg.what = MSG_DOWNLOAD_ERROR;
                msg.obj = ie;
                getDownloadHandler().sendMessage(msg);
            }
        }
    }

    /**
     * Background task for moving the currently selected resource to the given target folder.
     */
    private class MoveDocumentTask extends AbstractSimpleAsyncTask<String, Void, Void> {

        /** Default constructor. */
        public MoveDocumentTask() {
            super(GoogleDocMain.this, "Moving element");
        }

        @Override
        protected Void run(final String... targetResourceId) throws Exception {
            final GoogleEntry selected = (GoogleEntry) getListAdapter().getItem(selectedContextPosition);
            final String sourceCollectionId = getCurrentCollectionId();
            if (!sourceCollectionId.equals(targetResourceId[0])) {
                getGoogleUtil().getAPI().moveDocument(selected.getSimpleResourceId(), sourceCollectionId,
                        targetResourceId[0]);
                getData().remove(selected);
            }
            return null;
        }

        @Override
        protected void postExecute(Void result) {
            refreshData();
        }
    }

    /**
     * Background task for copying the currently selected resource.
     */
    private class CopyDocumentTask extends AbstractSimpleAsyncTask<GoogleEntry, Void, Void> {

        private String newName;

        /**
         * Default constructor.
         * @param newName name for the copy
         */
        public CopyDocumentTask(String newName) {
            super(GoogleDocMain.this, "Copying element");
            this.newName = newName;
        }

        @Override
        protected Void run(GoogleEntry... selected) throws Exception {
            getGoogleUtil().getAPI().copyDocument(selected[0].getSimpleResourceId(), newName, getCurrentCollectionId());
            return null;
        }

        @Override
        protected void postExecute(Void result) {
            executeRefreshDocuments();
        }
    }

    /**
     * Background task for deleting the currently selected resource.
     */
    private class DeleteEntryTask extends AbstractSimpleAsyncTask<GoogleEntry, Void, Void> {

        /**
         * Default constructor.
         */
        public DeleteEntryTask() {
            super(GoogleDocMain.this, "Deleting element");
        }

        @Override
        protected Void run(GoogleEntry... selected) throws Exception {
            getGoogleUtil().getAPI().deleteEntry(getDb(), selected[0]);
            return null;
        }

        @Override
        protected void postExecute(Void result) {
            getData().remove(selectedContextPosition);
            refreshData();
        }
    }

    /**
     * Callback for account selection.
     */
    class DocsOrganizerAccountManagerCallback implements AccountManagerCallback<Bundle> {
        /** {@inheritDoc} */
        public void run(AccountManagerFuture<Bundle> future) {
            setTitle(TITLE_MAIN);
            try {
                final Bundle bundle = future.getResult();
                final String account = bundle.getString(AccountManager.KEY_ACCOUNT_NAME);
                final String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                getGoogleUtil().setAccountName(account);
                getGoogleUtil().setAuthToken(token);
                if (account != null && token == null) {
                    //this happens after the creation of a new account -
                    //there for call chooseAccount again to use that account
                    chooseAccount();
                } else {
                    firstAccountSelection = false;
                    executeRefreshDocuments();
                }
            } catch (UserCanceledOperation e) {
                if (firstAccountSelection) {
                    finish();
                }
                updateDataAfterAccountSelection();
            } catch (OperationCanceledException e) {
                updateDataAfterAccountSelection();
                DialogUtil.showMessageDialog(GoogleDocMain.this, "Error",
                        "Choosing the account was canceled or the access was denied", new Runnable() {
                            public void run() {
                                if (firstAccountSelection) {
                                    finish();
                                }
                            }
                        });
            } catch (AuthenticatorException e) {
                handleAccountException(e);
            } catch (IOException e) {
                handleAccountException(e);
            }
        }
    }
}
